package no.sachmuld.methodcombination.api;

import org.junit.Assert;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 *
 */
public abstract class CrosscuttingTestTemplate {

    @Test
    public void shouldExecuteBehaviourCrosscuttingMethodStyle() throws Exception {
        List<Step> expected = Arrays.asList(Step.AROUND_Y_BEGIN, Step.AROUND_X_BEGIN, Step.BEFORE_Y, Step.BEFORE_X, Step.Y, Step.X, Step.AFTER_X, Step.AFTER_Y, Step.AROUND_X_END, Step.AROUND_Y_END);
        final List<Step> actual = new ArrayList<>();

        Foo foo = createFoo();

        foo.with(new FooBehaviour() {

            @Override
            public Object around(MethodInvocation<?> methodInvocation) {
                actual.add(Step.AROUND_X_BEGIN);
                try {
                    return methodInvocation.proceed();
                } finally {
                    actual.add(Step.AROUND_X_END);
                }
            }

            @Override
            public void before(MethodInvocation<Void> methodInvocation) {
                actual.add(Step.BEFORE_X);
            }

            @Override
            public Result bar(MethodInvocation<Result> methodInvocation) {
                actual.add(Step.X);
                return null;
            }

            @Override
            public void after(MethodInvocation<Void> methodInvocation) {
                actual.add(Step.AFTER_X);
            }

        });
        foo.with(new FooBehaviour() {

            @Override
            public Object around(MethodInvocation<?> methodInvocation) {
                actual.add(Step.AROUND_Y_BEGIN);
                try {
                    return methodInvocation.proceed();
                } finally {
                    actual.add(Step.AROUND_Y_END);
                }
            }

            @Override
            public void before(MethodInvocation<Void> methodInvocation) {
                actual.add(Step.BEFORE_Y);
            }

            @Override
            public Result bar(MethodInvocation<Result> methodInvocation) {
                actual.add(Step.Y);
                methodInvocation.proceed();
                return null;
            }

            @Override
            public void after(MethodInvocation<Void> methodInvocation) {
                actual.add(Step.AFTER_Y);
            }

        });

        foo.bar();

        Assert.assertEquals(expected, actual);

    }

    @Test
    public void shouldReturnValueFromMostSpecificPrimaryMethod() throws Exception {
        Result expected = Result.B;

        Foo foo = createFoo();

        foo.with(new FooBehaviour() {
            @Override
            public Result bar(MethodInvocation<Result> methodInvocation) {
                return Result.A;
            }
        });
        foo.with(new FooBehaviour() {
            @Override
            public Result bar(MethodInvocation<Result> methodInvocation) {
                return Result.B;
            }
        });

        Result actual = foo.bar();

        Assert.assertEquals(expected, actual);

    }

    static public enum Step {
        AROUND_X_BEGIN, AROUND_X_END, BEFORE_X, X, AFTER_X, AROUND_Y_BEGIN, AROUND_Y_END, BEFORE_Y, Y, AFTER_Y
    }

    static public enum Result {
        A, B
    }

    static public abstract class FooBehaviour extends CrosscuttingBehaviour {
        public Result bar(MethodInvocation<Result> methodInvocation) {
            return methodInvocation.proceed();
        }
    }

    static public abstract class Foo extends MethodCombinationSupport<FooBehaviour> {
        public abstract Result bar();
    }

    protected abstract Foo createFoo();

}
